from collections import abc
import itertools
import operator
from random import randint


# 可迭代的对象、迭代器和生成器
# 1、Sentence类第1版：单词序列
# @sentence.py
# 序列可以迭代的原因：iter函数 解释器需要迭代对象x时自动调用iter(x)
# iter函数
# 对象实现了__iter__方法则调用它获取一个迭代器
# 没有__iter__方法但实现了__getitem__方法，则创建一个迭代器按顺序获取元素
# 失败则TypeError
class Foo:
    def __iter__(self):
        pass


print(issubclass(Foo, abc.Iterable))
f = Foo()
print(isinstance(f, abc.Iterable))
# 2、可迭代的对象与迭代器的对比
# python从可迭代对象中获取迭代器
s = 'ABC'
for char in s:
    print(char)
it = iter(s)
while True:
    try:
        print(next(it))
    except StopIteration:
        del it
        break


# 3、Sentence类第2版：典型的迭代器
#  @see sentence_iter.py

# 4、Sentence类第3版：生成器函数
# @see sentence_gen.py
# 只要python函数的定义体中又yield关键字，该函数就是生成器函数
# 调用生成器函数时，会返回一个生成器对象，也就是说，生成器函数是生成器工厂
def gen_123():
    yield 1
    yield 2
    yield 3


print(gen_123)
print(gen_123())
for i in gen_123():
    print(i)
g = gen_123()
print(next(g))
print(next(g))
print(next(g))


def gen_AB():
    print('start')
    yield 'A'  # for 1
    print('continue')
    yield 'B'  # for 2
    print('end')  # for 3 catch StopIteration Error and stop


for c in gen_AB():
    print('--->', c)
# 5、Sentence类第4版：惰性实现
# @see sentence_gen2.py
# 6、Sentence类第5版：生成器表达式
# 生成器表达式可以理解为列表推导的惰性版本
# @see sentence_genxp.py
res1 = [x * 3 for x in gen_AB()]
for i in res1:
    print("--->", i)

res2 = (x * 3 for x in gen_AB())
print(res2)
for i in res2:
    print("--->", i)


# 7、何时使用生成器表达式
# 如果生成器表达式需要分成多行，则用函数，且可复用
# 否则 生成器表达式(出入一个参数的函数可省略括号)

# 8、另一个示例：等差数列生成器
# @see arithmetic_progression.py
# 如果一个类只是为了构建生成器去实现__iter__，不如使用生成器函数
def aritprog_gen(begin, step, end=None):
    result = type(begin + step)(begin)
    forever = end is None
    index = 0
    while forever or result < end:
        yield result
        index += 1
        result = begin + step * index


print(list(aritprog_gen(1, .5, 3)))
# itertools 模块提供了19个生成器函数
gen = itertools.count(1, .5)  # 没有end 不停止
print(next(gen))
print(next(gen))
print(next(gen))
print(next(gen))
# itertools.takewhile 生成一个使用另一个生成器的生成器 在指定条件为false是停止
gen = itertools.takewhile(lambda a: a < 3, itertools.count(1, .5))
print(list(gen))


def airtprog_gen_v2(begin, step, end=None):
    first = type(begin + step)(begin)
    ap_gen = itertools.count(first, step)
    if end is not None:
        ap_gen = itertools.takewhile(lambda n: n < end, ap_gen)
    return ap_gen


print(list(airtprog_gen_v2(1, .5, 3)))


# 9、标准库中的生成器函数
def vowel(c):
    return c.lower() in 'aeiou'


# 过滤
# 把 it 中的各个元素传给 predicate，如果predicate(item) 返回真值，那么产出对应的元素；如果 predicate 是 None，那么只产出真值元素
print(list(filter(vowel, 'Aardvark')))
# 与 filter 函数的作用类似，不过 predicate 的逻辑是相反的：predicate 返回假值时产出对应的元素
print(list(itertools.filterfalse(vowel, 'Aardvark')))
# 处理 it，跳过 predicate 的计算结果为真值的元素，然后产出剩下的各个元素（不再进一步检查）
print(list(itertools.dropwhile(vowel, 'Aardvark')))
# predicate 返回真值时产出对应的元素，然后立即停止，不再继续检查
print(list(itertools.takewhile(vowel, 'Aardvark')))
# 并行处理两个可迭代的对象 如果 selector_it中的元素是真值，产出 it 中对应的元素
print(list(itertools.compress('Aardvark', [1, 0, 1, 1, 0, 1])))
# 产出 it 的切片，作用类似于 s[:stop] 或 s[start:stop:step]，不过 it 可以是任何可迭代的对象，而且这个函数实现的是惰性操作
print(list(itertools.islice('Aardvark', 4)))
print(list(itertools.islice('Aardvark', 4, 7)))
print(list(itertools.islice('Aardvark', 1, 7, 2)))
# 映射
# 产出累积的总和；如果提供了 func，那么把前两个元素传给它，然后把计算结果和下一个元素传给它，以此类推，最后产出结果
sample = [5, 4, 2, 8, 7, 6, 3, 0, 9, 1]
print(list(itertools.accumulate(sample)))
print(list(itertools.accumulate(sample, min)))
print(list(itertools.accumulate(sample, max)))
print(list(itertools.accumulate(sample, operator.mul)))
print(list(itertools.accumulate(range(1, 11), operator.mul)))  # 从 1! 到 10!，计算各个数的阶乘
# 产出由两个元素组成的元组，结构是 (index,item)，其中 index 从 start 开始计数，item 则从iterable 中获取
print(list(enumerate('albatroz', 1)))
# 把 it 中的各个元素传给func，产出结果；如果传入N 个可迭代的对象，那么 func 必须能接受 N 个参数，而且要并行处理各个可迭代的对象
print(list(map(operator.mul, range(11), range(11))))
print(list(map(operator.mul, range(11), [2, 4, 8])))
print(list(map(lambda a, b: (a, b), range(11), [2, 4, 8])))  # 作用等同于内置的 zip 函数
# 把 it 中的各个元素传给 func，产出结果；输入的可迭代对象应该产出可迭代的元素 iit，然后以func(*iit) 这种形式调用 func
print(list(itertools.starmap(operator.mul, enumerate('albatroz', 1))))
sample = [5, 4, 2, 8, 7, 6, 3, 0, 9, 1]
print(list(itertools.starmap(lambda a, b: b / a, enumerate(itertools.accumulate(sample), 1))))  # 计算平均值
# 合并
# chain 先产出 it1 中的所有元素，然后产出 it2 中的所有元素，以此类推，无缝连接在一起
print(list(itertools.chain('ABC', range(2))))
print(list(itertools.chain(enumerate('ABC'))))  # 只传入一个可迭代的对象，那么 chain 函数没什么用
# 产出 it 生成的各个可迭代对象中的元素，一个接一个，无缝连接在一起；it 应该产出可迭代的元素，例如可迭代的对象列表
print(list(itertools.chain.from_iterable(enumerate('ABC'))))
# 并行从输入的各个可迭代对象中获取元素，产出由 N 个元素组成的元组，只要有一个可迭代的对象到头了，就默默地停止
print(list(zip('ABC', range(5))))
print(list(zip('ABC', range(5), [10, 20, 30, 40])))
# 并行从输入的各个可迭代对象中获取元素，产出由 N 个元素组成的元组，等到最长的可迭代对象到头后才停止，空缺的值使用fillvalue填充
print(list(itertools.zip_longest('ABC', range(5))))
print(list(itertools.zip_longest('ABC', range(5), fillvalue='?')))
# 计算笛卡儿积：从输入的各个可迭代对象中获取元素，合并成由 N 个元素组成的元组，与嵌套的 for 循环效果一样；repeat 指明重复处理多少次输入的可迭代对象
print(list(itertools.product('ABC', range(2))))
print(list(itertools.product('ABC')))
print(list(itertools.product('ABC', repeat=2)))  # 重复 N 次处理输入的各个可迭代对象
print(list(itertools.product(range(2), repeat=3)))  # 重复 N 次处理输入的各个可迭代对象
# 会从一个元素中产出多个值，扩展输入的可迭代对象
# count 从 start 开始不断产出数字，按step 指定的步幅增加
ct = itertools.count()
print(next(ct))
print(next(ct), next(ct), next(ct))
print(list(itertools.islice(itertools.count(1, .3), 3)))
# cycle 从 it 中产出各个元素，存储各个元素的副本，然后按顺序重复不断地产出各个元素
cy = itertools.cycle('ABC')
print(next(cy))
print(list(itertools.islice(cy, 7)))
# repeat 重复不断地产出指定的元素，除非提供 times，指定次数
rp = itertools.repeat(7)
print(next(rp), next(rp))
print(list(itertools.repeat(8, 4)))
print(list(map(operator.mul, range(11), itertools.repeat(5))))
# combinations 把 it 产出的 out_len 个元素组合在一起，然后产出
print(list(itertools.combinations('ABC', 2)))  # 'ABC' 中每两个元素（len()==2）的各种组合
# combinations_with_replacement 把 it 产出的 out_len 个元素组合在一起，然后产出，包含相同元素的组合
print(list(itertools.combinations_with_replacement('ABC', 2)))  # 'ABC' 中每两个元素（len()==2）的各种组合，包括相同元素的组合
# permutations 把 out_len 个 it 产出的元素排列在一起，然后产出这些排列；out_len的默认值等于 len(list(it))
print(list(itertools.permutations('ABC', 2)))  # 'ABC' 中每两个元素（len()==2）的各种排列；在生成的元组中，元素的顺序有重要意义
print(list(itertools.product('ABC', repeat=2)))  # 'ABC' 和 'ABC'（repeat=2 的效果）的笛卡儿积
# 用于产出输入的可迭代对象中的全部元素，不过会以某种方式重新排列 其中有两个函数会返回多个生成器，分别是 itertools.groupby 和 itertools.tee
# groupby 产出由两个元素组成的元素，形式为 (key,group)，其中 key 是分组标准，group 是生成器，用于产出分组里的元素
print(list(itertools.groupby('LLLLAAGGG')))  # 产出 (key, group_generator) 这种形式的元组
for char, group in itertools.groupby('LLLLAAGGG'):
    print(char, '->', list(group))
animals = ['duck', 'eagle', 'rat', 'giraffe', 'bear', 'bat', 'dolphin', 'shark', 'lion']
animals.sort(key=len)
print(animals)
for length, group in itertools.groupby(animals, len):
    print(length, '->', list(group))
# reversed 从后向前，倒序产出 seq 中的元素；seq 必须是序列，或者是实现了 __reversed__ 特殊方法的对象
for length, group in itertools.groupby(reversed(animals), len):
    print(length, '->', list(group))
# tee 产出一个由 n 个生成器组成的元组，每个生成器用于单独产出输入的可迭代对象中的元素
print(list(itertools.tee('ABC')))
g1, g2 = itertools.tee('ABC')
print(next(g1))
print(next(g2))
print(next(g2))
print(list(g1))
print(list(g2))
print(list(zip(*itertools.tee('ABC'))))


# 10、Python 3.3中新出现的句法：yield from
# 如果生成器函数需要产出另一个生成器生成的值 传统 for
def chain(*iterables):
    for it in iterables:
        for i in it:
            yield i


s = 'ABC'
t = tuple(range(3))
print(list(chain(s, t)))


# yield from i 完全代替了内层的 for 循环
def chain_from(*iterables):
    for i in iterables:
        yield from i


print(list(chain_from(s, t)))

# 11、可迭代的归约函数
# all it 中的所有元素都为真值时返回 True，否则返回 False；all([]) 返回 True
print(all([1, 2, 3]))
print(all([1, 0, 3]))
print(all([]))
# any 只要 it 中有元素为真值就返回 True，否则返回 False； any([]) 返回 False
print(any([1, 2, 3]))
print(any([1, 0, 3]))
print(any([0, 0.0]))
print(any([]))
g = (n for n in [0, 0.0, 7, 8])
print(any(g))
print(next(g))


# max(itm,[key=] [default=]) 返回 it 中值最大的元素；*key 是排序函数，与 sorted 函数中的一样；如果可迭代的对象为空，返回 default
# min(itm,[key=] [default=]) 返回 it 中值最小的元素；key 是排序函数，与 sorted 函数中的一样；如果可迭代的对象为空，返回 default
# reduce(func,it,[initial]) 把前两个元素传给func，然后把计算结果和第三个元素传给 func，以此类推，返回最后的结果；如果提供了initial，把它当作第一个元素传入
# sum(it,start=0) it 中所有元素的总和，如果提供可选的 start，会把它加上（计算浮点数的加法时，可以使用 math.fsum 函数提高精度）

# 12、深入分析iter函数
def d6():
    return randint(1, 6)


d6_iter = iter(d6, 1)
print(d6_iter)
for roll in d6_iter:
    print(roll)

# 13、案例分析：在数据库转换工具中使用生成器
# 14、把生成器当成协程
# .send() 方法
